//
//  ImageDownLoader.swift
//  
//
//  Created by xp on 2023/4/3.
//

import Foundation

// URLSession介绍:
// https://www.jianshu.com/p/695d50c30b1a

public class ImageDownLoader {
    
    public static let `default` = ImageDownLoader("default")
    
    private var identifier: String
    public var session: URLSession
    public var delegate = HKSessionDelegate()
    
    // default:
    // 默认模式，通常我们用这种模式就足够了。default模式下系统会创建一个持久化的缓存并在用户的钥匙串中存储证书。
    // ephemeral:
    // 系统没有任何持久性存储，所有内容的生命周期都与session相同，当session无效时，所有内容自动释放。
    
    // background:
    // 创建一个可以在后台甚至APP已经关闭的时候仍然在传输数据的会话。
    // background模式与default模式非常相似，不过background模式会用一个独立线程来进行数据传输。
    // background模式可以在程序挂起，退出，崩溃的情况下运行task。也可以利用标识符来恢复进。
    // 注意，后台Session一定要在创建的时候赋予一个唯一的identifier，这样在APP下次运行的时候，能够根据identifier来进行相关的区分。
    // 如果用户关闭了APP,IOS 系统会关闭所有的background Session。而且，被用户强制关闭了以后，IOS系统不会主动唤醒APP，只有用户下次启动了APP，数据传输才会继续。
    public var configuration = URLSessionConfiguration.ephemeral
    
    public var timeoutInterval: TimeInterval = 10
    
    init(_ identifier: String) {
        self.identifier = identifier
        
        session = .init(configuration: configuration, delegate: delegate, delegateQueue: nil)
    }
    
}

public extension ImageDownLoader {
    
    @discardableResult
    func down(source: String, progressBlock: DownloadProgressBlock? = nil, completion: VoidBlock? = nil) -> HKSessionTask? {
        guard let url = URL(string: source) else {
            return nil
        }
        // reloadIgnoringLocalCacheData:
        // URL 加载应该只从原始源加载。
        // 此策略指定不应使用现有缓存数据来满足 URL 加载请求。
        var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: timeoutInterval)
        // 指示请求是否应在收到先前的响应之前传输。
        // 将该属性设置为 true 可以极大的提高网络性能，但是 HTTPShouldUsePipelining 也有其局限性，就是服务器必须按照收到请求的顺序返回对应的数据
        request.httpShouldUsePipelining = false
        
        if #available(iOS 13.0, *) {
            // false:设置为低数据模式时，请求会失败
            request.allowsConstrainedNetworkAccess = false
        }
        
        let dataTask = session.dataTask(with: request)
        
        print("resume data task id:",dataTask.taskIdentifier)
        let task = delegate.appendTask(urlString: url.absoluteString, dataTask)
        task.callBack = {
            progressBlock?(Int64(task.receivedData.count),task.task.response?.expectedContentLength ?? 0, task.receivedData)
        }
        task.completionBlock = completion
        task.resume()
        return task
    }
    
}



public class HKSessionDelegate: NSObject, URLSessionDelegate {
    
    private(set) var dataTask: [String: HKSessionTask] = [:]
    
    @discardableResult
    fileprivate func appendTask(urlString: String,_ task: URLSessionTask) -> HKSessionTask {
        let task = HKSessionTask(task: task)
        dataTask[urlString] = task
        return task
    }
    @discardableResult
    fileprivate func removeTask(sessionTask: URLSessionTask) -> HKSessionTask? {
        guard let urlString = sessionTask.originalRequest?.url?.absoluteString else {
            return nil
        }
        return dataTask.removeValue(forKey: urlString)
    }
    
    func getTask(sessionTask: URLSessionTask) -> HKSessionTask? {
        guard let url = sessionTask.originalRequest?.url?.absoluteString else {
            return nil
        }
        guard let task = dataTask[url] else {
            return nil
        }
        if sessionTask.taskIdentifier == task.task.taskIdentifier {
            return task
        }
        return nil
    }
    
}

extension HKSessionDelegate: URLSessionDataDelegate {
    public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
//        print(#function, dataTask.taskIdentifier, data.count, dataTask.response?.expectedContentLength)
        
        guard let task = getTask(sessionTask: dataTask) else {
            return
        }
        task.addData(data)
        task.callBack?()
        
    }
    // 请求结束
    public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        let hkTask = removeTask(sessionTask: task)
        hkTask?.completionBlock?()
        hkTask?.callBack = nil
        hkTask?.completionBlock = nil
    }
}
